/**
 * 
 */
package ro.dta.idbi.model;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import ro.dta.idbi.interfaces.IntegrationOperation;
import ro.dta.idbi.interfaces.sql.ISqlColumn;
import ro.dta.idbi.interfaces.sql.ISqlTable;
import uk.ac.ic.doc.automed.reps.Schema;

/**
 * Utilities class
 * 
 * @author Tudor Dobrila
 * 
 */
public final class Utils {

	private static final String NOTEPAD = "\"C:\\WINDOWS\\system32\\notepad.exe\"";

	/**
	 * Get all defined patterns for a given integration operation
	 * 
	 * @param operation
	 *            Integration operation used to retrieve patterns for (can be
	 *            CONFORMING, MERGING or IMPROVING)
	 * @return List of patterns defined for this operation
	 */
	public static List<Pattern> getPatterns(final IntegrationOperation operation)
			throws ClassNotFoundException, IOException {
		final ArrayList<Pattern> result = new ArrayList<Pattern>();
		final Class<?>[] classes1 = getClasses("ro.dta.idbi.patterns");
		final Class<?>[] classes2 = getClasses("ro.dta.idbi.patterns.primitives");
		final Class<?>[] classes = new Class<?>[classes1.length + classes2.length];
		System.arraycopy(classes1, 0, classes, 0, classes1.length);
		System.arraycopy(classes2, 0, classes, classes1.length, classes2.length);

		for (Class<?> c : classes) {
			if (!Modifier.isAbstract(c.getModifiers())
					&& (c.getSuperclass().equals(AbstractPattern.class) || c.getSuperclass()
							.equals(AbstractPrimitivePattern.class))) {
				try {
					Field field = c.getField("NAME");
					final String patternName = field.get(null).toString();
					field = c.getField("OPERATION");
					Object oper = field.get(null);
					if (oper instanceof EnumSet<?>) {
						@SuppressWarnings("unchecked")
						final EnumSet<IntegrationOperation> crtOper = (EnumSet<IntegrationOperation>) oper;
						if (crtOper.contains(operation)) {
							result.add(new Pattern(c, patternName, crtOper));
						}
					}
				} catch (Exception e) {
					// DO NOTHING
					final Logger logger = Logger.getLogger("ro.dta.idbi.model.Utils");
					logger.log(Level.WARNING, e.getMessage());
				}
			}
		}

		return result;
	}

	/**
	 * Get all defined patterns for a given set of integration operations
	 * 
	 * @param operations
	 *            Integration operations set used to retrieve patterns for (can
	 *            be CONFORMING, MERGING or IMPROVING)
	 * @return List of patterns defined for one of the operations passed as
	 *         parameter
	 */
	public static List<Pattern> getPatterns(final EnumSet<IntegrationOperation> operations)
			throws ClassNotFoundException, IOException {
		final Iterator<IntegrationOperation> iter = operations.iterator();
		final ArrayList<Pattern> result = new ArrayList<Pattern>();

		while (iter.hasNext()) {
			final IntegrationOperation crtOp = iter.next();
			result.addAll(getPatterns(crtOp));
		}

		return result;
	}

	/**
	 * Get all classes in a given package
	 * 
	 * @param packageName
	 *            Name of the package to search
	 * @return The classes in the package, excluding subpackages
	 */
	public static Class<?>[] getClasses(final String packageName) throws ClassNotFoundException,
			IOException {
		final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
		final String path = packageName.replace('.', '/');
		final Enumeration<URL> resources = classLoader.getResources(path);
		final List<File> dirs = new ArrayList<File>();

		while (resources.hasMoreElements()) {
			final URL resource = resources.nextElement();
			dirs.add(new File(URLDecoder.decode(resource.getPath(), "UTF-8")));
		}
		final ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
		for (File directory : dirs) {
			final File[] files = directory.listFiles();
			if (files != null) {
				for (File file : files) {
					if (file.getName().endsWith(".class")) {
						classes.add(Class.forName(packageName + '.'
								+ file.getName().substring(0, file.getName().length() - 6)));
					}
				}
			}
		}
		return classes.toArray(new Class[classes.size()]);
	}

	/**
	 * Open the AutoMed configuration file
	 * 
	 * @throws IOException
	 */
	public static void openConfiguration() throws IOException {
		// Edit configuration file
		final String cfgFile = System.getProperty("user.home")
				+ System.getProperty("file.separator") + ".automed"
				+ System.getProperty("file.separator") + "data_source_repository.cfg";

		if (new File(cfgFile).exists()) {
			final Runtime runtime = Runtime.getRuntime();
			runtime.exec(NOTEPAD + " " + cfgFile);
		}
	}

	/**
	 * Get the common supertype of the tables
	 * 
	 * @param schema
	 *            Schema in the context of which to check for the supertype
	 * @param objects
	 *            Set of objects
	 * @return A table that is the parent of the other tables, if it exists,
	 *         null otherwise
	 */
	public static ISqlTable getCommonSupertype(final Schema schema, final ISqlTable[] objects) {
		// Check if one of the tables if supertype of the others
		final ArrayList<List<String>> supertypes = new ArrayList<List<String>>();
		for (ISqlTable o : objects) {
			supertypes.add(o.getSupertypesNames());
		}

		// Check if one of the selected tables is the parent table of the other
		// tables
		ISqlTable parentTbl = null;
		for (int i = 0; i < objects.length; i++) {
			// Calculate the supertype intersection of all tables except for the
			// ith table
			final ISqlTable crtTbl = objects[i];
			List<String> intersection = null;
			for (int j = 0; j < objects.length; j++) {
				if (i == j) {
					continue;
				}

				final List<String> crtSuper = supertypes.get(j);
				if (intersection == null) {
					intersection = crtSuper;
				} else {
					final ArrayList<String> newIntersection = new ArrayList<String>(intersection);
					for (String s1 : intersection) {
						if (!crtSuper.contains(s1)) {
							newIntersection.remove(s1);
						}
					}
					intersection = newIntersection;
				}
			}

			if (intersection.contains(crtTbl.getValue().toString())) {
				parentTbl = crtTbl;
				break;
			}
		}

		return parentTbl;
	}

	/**
	 * Construct the patterns for <i>add</i>/<i>extend</i> or
	 * <i>delete</i>/<i>Contract</i> BAV transformations
	 * 
	 * @param primitiveCmd
	 *            Command (can be <i>add</i>/<i>extend</i> or
	 *            <i>delete</i>/<i>contract</i>)
	 * @return Patterns for the given command
	 */
	public static Hashtable<String, java.util.regex.Pattern> constructPatterns(String primitiveCmd) {
		String base = "^" + primitiveCmd + "[ ]*\\(";
		Hashtable<String, java.util.regex.Pattern> patterns = new Hashtable<String, java.util.regex.Pattern>();
		patterns.put(
				primitiveCmd + "table",
				java.util.regex.Pattern.compile(base + AbstractPattern.TABLE_DEF_REGEX
						+ AbstractPattern.COMMA_REGEX + "([^\\)]+)" + "\\)$"));
		patterns.put(
				primitiveCmd + "column",
				java.util.regex.Pattern.compile(base + AbstractPattern.COL_DEF_REGEX
						+ AbstractPattern.COMMA_REGEX + "([^\\)]+)\\)$"));
		patterns.put(primitiveCmd + "primarykey",
				java.util.regex.Pattern.compile(base + AbstractPattern.PK_DEF_REGEX + "\\)$"));
		patterns.put(primitiveCmd + "foreignkey",
				java.util.regex.Pattern.compile(base + AbstractPattern.FK_DEF_REGEX + "\\)$"));
		return patterns;
	}

	/**
	 * Construct the patterns for <i>rename</i> BAV transformations
	 * 
	 * @return Patterns for the rename command
	 */
	public static Hashtable<String, java.util.regex.Pattern> constructRenamePatterns() {
		String base = "^rename[ ]*\\(";
		Hashtable<String, java.util.regex.Pattern> patterns = new Hashtable<String, java.util.regex.Pattern>();
		patterns.put(
				"renametable",
				java.util.regex.Pattern.compile(base + AbstractPattern.TABLE_DEF_REGEX
						+ AbstractPattern.COMMA_REGEX + AbstractPattern.TABLE_DEF_REGEX + "\\)$"));
		patterns.put(
				"renamecolumn",
				java.util.regex.Pattern.compile(base + "column:(" + AbstractPattern.COL_REGEX + ")"
						+ AbstractPattern.COMMA_REGEX + "column:(" + AbstractPattern.COL_REGEX
						+ ")\\)$"));
		patterns.put(
				"renameprimarykey",
				java.util.regex.Pattern.compile(base + AbstractPattern.PK_DEF_REGEX
						+ AbstractPattern.COMMA_REGEX + AbstractPattern.PK_DEF_REGEX + "\\)$"));
		patterns.put(
				"renameforeignkey",
				java.util.regex.Pattern.compile(base + AbstractPattern.FK_DEF_REGEX
						+ AbstractPattern.COMMA_REGEX + AbstractPattern.FK_DEF_REGEX + "\\)$"));
		return patterns;
	}

	/**
	 * Generate a BAV representation of a table, given its name
	 * 
	 * @param tblName
	 *            Name of the table
	 * @return BAV representation of the table
	 */
	public static String genTableRepresentation(String tblName) {
		StringBuilder builder = new StringBuilder();
		builder.append("table:<<").append(tblName).append(">>");
		return builder.toString();
	}

	/**
	 * Generate a BAV representation of a column, given its name
	 * 
	 * @param tblName
	 *            Name of the table containing the column
	 * @param colName
	 *            Name of the column
	 * @param nullable
	 *            Mandatory option of the column (can be <i>null</i> or
	 *            <i>notnull</i>)
	 * @param type
	 *            Type of the column
	 * @return BAV representation of the column
	 */
	public static String genColRepresentation(Object tblName, Object colName, Object nullable,
			Object type) {
		StringBuilder builder = new StringBuilder();
		builder.append("column:<<").append(tblName).append(",").append(colName);
		builder.append(",").append(nullable).append(",").append(type);
		builder.append(">>");
		return builder.toString();
	}

	/**
	 * Generate a BAV representation of a FK from source table to target table,
	 * containing identically named columns
	 * 
	 * @param sourceTable
	 *            Source table
	 * @param targetTable
	 *            Target table
	 * @param cols
	 *            List of columns in the FK
	 * @return BAV representation of FK
	 */
	public static String genFKRepresentation(String sourceTable, String targetTable,
			List<String> cols) {
		return genFKRepresentation(sourceTable, targetTable, cols, cols);
	}

	/**
	 * Generate a BAV representation of a FK from source table to target table
	 * 
	 * @param sourceTable
	 *            Source table
	 * @param targetTable
	 *            Target table
	 * @param sourceCols
	 *            List of columns in the FK in the source table
	 * @param targetCols
	 *            List of columns in the FK in the target table
	 * @return BAV representation of the FK
	 */
	public static String genFKRepresentation(String sourceTable, String targetTable,
			List<String> sourceCols, List<String> targetCols) {
		return genFKRepresentation(sourceTable + "_isa_" + targetTable + "_fk", sourceTable,
				targetTable, sourceCols, targetCols);

	}

	/**
	 * Generate a BAV representation of a FK from source table to target table
	 * 
	 * @param fkName
	 *            Name of the newly introduced FK
	 * @param sourceTable
	 *            Source table
	 * @param targetTable
	 *            Target table
	 * @param sourceCols
	 *            List of columns in the FK in the source table
	 * @param targetCols
	 *            List of columns in the FK in the target table
	 * @return BAV representation of the FK
	 */
	public static String genFKRepresentation(String fkName, String sourceTable, String targetTable,
			List<String> sourceCols, List<String> targetCols) {
		StringBuilder builder = new StringBuilder();
		builder.append("foreignkey:<<");
		builder.append(fkName).append(",");
		builder.append(sourceTable).append(",");
		for (int i = 0; i < sourceCols.size(); i++) {
			builder.append("<<").append(sourceTable).append(",").append(sourceCols.get(i))
					.append(">>,");
		}
		builder.append(targetTable).append(",");
		for (int i = 0; i < targetCols.size(); i++) {
			builder.append("<<").append(targetTable).append(",").append(targetCols.get(i))
					.append(">>,");

		}
		builder.setLength(builder.length() - 1);
		builder.append(">>");

		return builder.toString();
	}
	
	/**
	 * Generate a BAV representation of a FK from source table to target table
	 * 
	 * @param fkName
	 *            Name of the newly introduced FK
	 * @param sourceTable
	 *            Source table
	 * @param targetTable
	 *            Target table
	 * @param sourceCols
	 *            List of columns in the FK in the source table
	 * @param targetCols
	 *            List of columns in the FK in the target table
	 * @return BAV representation of the FK
	 */
	public static String genFKRepresentation(String fkName, ISqlTable sourceTable, ISqlTable targetTable,
			List<ISqlColumn> sourceCols, List<ISqlColumn> targetCols) {
		StringBuilder builder = new StringBuilder("foreignkey:<<");
		builder.append(fkName).append(",");
		builder.append(sourceTable.getValue()).append(",");
		for (ISqlColumn col : sourceCols) {
			builder.append(col.getShortDefinition()).append(",");
		}
		builder.append(targetTable.getValue()).append(",");
		for (ISqlColumn col : targetCols) {
			builder.append(col.getShortDefinition()).append(",");
		}
		builder.setLength(builder.length() - 1);
		builder.append(">>");

		return builder.toString();
	}

	/**
	 * Generate a BAV representation of a PK, expressed as an array of objects
	 * 
	 * @param pkArr
	 *            Array of objects describing the PK
	 * @return BAV representation of the PK
	 */
	public static String genPKRepresentation(Object[] pkArr) {
		StringBuilder builder = new StringBuilder();
		builder.append("primarykey:<<");
		builder.append(arrayToString(pkArr));
		builder.append(">>");
		return builder.toString();
	}

	/**
	 * Convert an array of objects into a textual representation, where objects
	 * are separated by a comma
	 * 
	 * @param arr
	 *            Array of objects
	 * @return Textual representation of the array of objects
	 */
	public static String arrayToString(Object[] arr) {
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < arr.length; i++) {
			builder.append(arr[i]).append(",");
		}
		builder.setLength(builder.length() - 1);
		return builder.toString();
	}

	/**
	 * Private constructor to avoid instatiating the class
	 */
	private Utils() {
	}
}
